package com.pankajm.gre;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;

import com.pankajm.common.Files;
import com.pankajm.common.IoHelper;

public class WordGame {
	
	private static final int DEFAULT_WORD_LIST = 1;
	private boolean adapt;
	private int numChoices;
	private double score = 0;
	private double attempts = 0;
	
	/*
	 * dynamically adjust number of choices based on current performance.
	 */
	private double[] thresholds = {
		0.0,
		0.125 /* from 0.0 to 0.125 choices = 1 (just learn new words) */, 
		0.25, /* from 0.125 to 0.25 choices = 2 and so on */
		0.5, 
		0.75, 
		0.875, 
		0.9375, 
		0.96875, 
		1.0};
	
	private int thresholdIndex = 3;
	private WordListLoader wlloader;
	private int wlnumber;
	private WordListJumbler jumbler;
	private Mode currentMode;
	private List<WordItem> wordList;
	
	enum Mode {
		/** adjust threshold and num choices based on score */
		CHALLENGE("challenge"),
		
		/** fix num choices = 1 */
		LEARN("learn"),
		
		/** if reply = option 0, i.e skip question, dont ask it again */
		FILTER("filter");
		
		private Mode(String name) {
			this.name = name;
		}
		
		public static Mode forName(String name) {
			for (Mode m : Mode.values()) {
				if (name.equals(m.name)) {
					return m;
				}
			}
			return null;
		}
		
		private String name;
	}
	
	public static void main(String[] args) throws IOException {
		WordGame wordGame = new WordGame();
		wordGame.setDefaults();
		
		long time = System.currentTimeMillis();

		System.out.println("Parsign command line arguments passed : ");
		boolean batchMode = false;
		String commandToRunInBatch = null;
		for (String s : args) {
			System.out.println(s);
			if (s.equals("-run")) {
				batchMode = true;
				continue;
			}
			
			if (batchMode) {
				commandToRunInBatch = s;
				break;
			}
		}
		
		if (batchMode && commandToRunInBatch != null) {
			wordGame.executeCommand(commandToRunInBatch);
			System.exit(0);
		}
		
		while (true) {
			System.out.print(">>");
			String command = IoHelper.readLine();
			
			if (command.equalsIgnoreCase("quit") || command.equalsIgnoreCase("exit")) {
				break;
			} else if (command.equalsIgnoreCase("help")) {
 			  wordGame.printHelpMessage();
			} else if (command.startsWith("mode")) {
				wordGame.currentMode = Mode.forName(command.substring(5).trim());
			} else if (command.equalsIgnoreCase("time")) {
				System.out.println("Delta time since start = " + (System.currentTimeMillis() - time) / 1000);
			} else if (command.equalsIgnoreCase("summary")) {
				wordGame.jumbler.summary(wordGame.currentMode);
			} else if (command.equalsIgnoreCase("save")) {
				wordGame.saveProgress();
			} else if (command.startsWith("adapt")) {
				String adapt = command.substring(6).trim();
				if (adapt.equalsIgnoreCase("on")) {
					wordGame.adapt = true;
				} else if (adapt.equalsIgnoreCase("off")) {
					wordGame.adapt = false;
				} else {
					System.out.println("Syntax error for adapt command."
						+ " Valid options for second word are on/off.");
				}
			} else if (command.startsWith("load")) {
				StringTokenizer tokenizer = new StringTokenizer(command);
				tokenizer.nextToken(); // load
				String src = tokenizer.nextToken();
				wordGame.wlnumber = Integer.parseInt(tokenizer.nextToken());
				if (src.equalsIgnoreCase("progress")) {
					wordGame.loadWordList(src);
				} else if (src.equalsIgnoreCase("golden")) {
					wordGame.loadWordList("wordlists");
				} else {
					System.out.println("Syntax error for load command."
						+ " Valid options for second word are progress/golden");
				}
			} else if (command.startsWith("showWordsInList")) {
				StringTokenizer tokenizer = new StringTokenizer(command);
				tokenizer.nextToken(); // token = "showWordsInList"
				String src = tokenizer.nextToken();
				while (tokenizer.hasMoreTokens()) {
					wordGame.wlnumber = Integer.parseInt(tokenizer.nextToken()); // token = wl number
					if (src.equalsIgnoreCase("progress")) {
						wordGame.showWordsInList(src);
					} else if (src.equalsIgnoreCase("golden")) {
						wordGame.showWordsInList("wordlists");
					} else {
						System.out.println("Syntax error for showWordsInList command."
							+ " Valid options for second word are progress/golden");
					}
				}
			} else if (command.startsWith("showAllWordsWithPrefix")) {
				StringTokenizer tokenizer = new StringTokenizer(command);
				tokenizer.nextToken(); // token = "showAllWordsWithPrefix"
				String prefix = tokenizer.nextToken();
				
				for (int i = 1; i <= 50; i++) {
					wordGame.wlnumber = i;
					wordGame.showAllWordsWithPrefix("wordlists", prefix);
				}
			} else if (command.startsWith("choices")) {
				wordGame.numChoices = Integer.parseInt(command.substring(8).trim());
			} else if (command.equalsIgnoreCase("score")) {
				System.out.println(wordGame.score + " / " + wordGame.attempts);
			} else {
				wordGame.askMcq();
			}
			
			if (wordGame.currentMode == Mode.CHALLENGE && wordGame.adapt) {
				wordGame.adjustThresholdAndNumChoices();
			} else if (wordGame.currentMode == Mode.LEARN) {
				wordGame.numChoices = 1;
			}
		}
	}
	
	private void executeCommand(String command) throws IOException {
		if (command.startsWith("showWordsInList")) {
			StringTokenizer tokenizer = new StringTokenizer(command);
			tokenizer.nextToken(); // token = "showWordsInList"
			String src = tokenizer.nextToken();
			while (tokenizer.hasMoreTokens()) {
				this.wlnumber = Integer.parseInt(tokenizer.nextToken()); // token = wl number
				if (src.equalsIgnoreCase("progress")) {
					this.showWordsInList(src);
				} else if (src.equalsIgnoreCase("golden")) {
					this.showWordsInList("wordlists");
				} else {
					System.out.println("Syntax error for showWordsInList command."
						+ " Valid options for second word are progress/golden");
				}
			}
		} else if (command.startsWith("showAllWordsWithPrefix")) {
			StringTokenizer tokenizer = new StringTokenizer(command);
			tokenizer.nextToken(); // token = "showAllWordsWithPrefix"
			String prefix = tokenizer.nextToken();
			
			for (int i = 1; i <= 50; i++) {
				this.wlnumber = i;
				this.showAllWordsWithPrefix("wordlists", prefix);
			}
		}
	}

	private void printHelpMessage() {
		System.out.println("======================================\n" + 
				"Type quit or exit to end this program.\n" + 
				"Just press enter (no command) to get the next word item\n" + 
				"\n" + 
				"Basic commands are described below:\n" + 
				"load {progress/golden} {n=1-50}\n" + 
				"			loads the wordlist with number given.\n" + 
				"			 E.g. load golden 2 loads the 2nd word list\n" + 
				"\n" + 
				"showWordsInList {progress/golden} {n=1-50}\n" +
				"			shows the words of the wordlist with number given.\n" + 
				"			 E.g. showWordsInList golden 2 shows the 2nd word list\n" + 
				"\n" + 			
				"showAllWordsWithPrefix prefix" +
				"			shows all words of 1-50 wordlists with given prefix string\n" +
				" 			which can be used as input to wget to fetch all files." +
				"summary\n" + 
				"			see a summary of your performance.\n" + 
				"\n" + 
				"score\n" + 
				"			gives the current score of the user.\n" + 
				"\n" + 
				"Advanced commands are described below:\n" + 
				"mode {challenge|learn|filter}\n" + 
				"			changes the mode of program.\n" + 
				"			 E.g. mode learn will change to learning mode where you\n" + 
				"			 will only be presented the word and the exact meaning.\n" + 
				"\n" + 
				"save\n" + 
				"			Type end and press enter after this to save\n" + 
				"			 the text between to another file in 'progress' folder\n" + 
				"			E.g\n" + 
				"			save\n" + 
				"			ablution-->washing ;\n" + 
				"			acme-->peak;  pinnacle;  highest point ;\n" + 
				"			end\n" + 
				"			Will save ablution and acme word meanings to a file.\n" + 
				"\n" + 
				"choices {n=1-8}\n" + 
				"			Change number of multiple choices to n\n" + 
				"\n" + 
				"======================================\n" + 
				"");
	}

	private void adjustThresholdAndNumChoices() {
		if ((score/attempts) > thresholds[thresholdIndex]) {
			numChoices++;
			thresholdIndex++;
		} else if ((score/attempts) < thresholds[thresholdIndex - 1]) {
			numChoices--;
			thresholdIndex--;
		}
	}

	private void askMcq() throws IOException {
		if (this.currentMode == Mode.LEARN || this.currentMode == Mode.FILTER) {
			jumbler.presentNextWordItem(currentMode);
			// attempts and score will be unchanged.
		} else {
			int result = jumbler.askJumbledQuery(numChoices);
			if (result != 0) {
				attempts++;
				if (result > 0) {
					score++;
				}
			}
		}
	}
	
	private void loadWordList(String dirPath) throws IOException {
		this.wlloader.loadWordList(dirPath, wlnumber);
		this.wordList = this.wlloader.getWordList(wlnumber);
		this.jumbler = new WordListJumbler(this.wordList, wlnumber);
	}

	private void showWordsInList(String dirPath) throws IOException {
		this.wlloader.loadWordList(dirPath, wlnumber);
		this.wordList = this.wlloader.getWordList(wlnumber);
		WordListUtils.printWordsOfList("http://www.etymonline.com/index.php?term=", this.wordList);
	}
	
	private void showAllWordsWithPrefix(String dirPath, String prefix) throws IOException {
		this.wlloader.loadWordList(dirPath, wlnumber);
		this.wordList = this.wlloader.getWordList(wlnumber);
		WordListUtils.printWordsOfList(prefix, this.wordList);
	}
	
	private void saveProgress() throws IOException {
		List<String> wordList = new LinkedList<String>();
		do {
			String wordItem = IoHelper.readLine();
			if (wordItem.equals("end")) {
				break;
			} else {
				wordList.add(wordItem);
			}
		} while(true);
		
		// write to "progress/WordList<n>.txt" now
		Files.writeLines(wordList, "progress", "WordList" + this.wlnumber + ".txt");
	}

	private void setDefaults() throws IOException {
		wlloader = new WordListLoader("wordlists");
		wlnumber = DEFAULT_WORD_LIST;
		wlloader.loadWordList(DEFAULT_WORD_LIST);
		List<WordItem> wordList = wlloader.getWordList(DEFAULT_WORD_LIST);
		jumbler = new WordListJumbler(wordList, wlnumber);
		currentMode = Mode.CHALLENGE;
		adapt = false;
		numChoices = 4;
	}
}
